package lol.clann.minecraft.springboot.api.model.message;

import lol.clann.minecraft.springboot.api.context.SpringContext;
import lol.clann.minecraft.springboot.api.model.CacheItem;
import lol.clann.minecraft.springboot.api.model.LazyField;
import lol.clann.minecraft.springboot.api.service.CacheService;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author pyz
 * @date 2019/6/23 11:15 AM
 */
public class MessageBuilder {
    private static final Pattern placeholderPattern = Pattern.compile("((?<!\\\\)\\$\\{.+?(?<!\\\\)\\})");
    private static final LazyField<CacheService> cacheService = LazyField.of(() -> SpringContext.getBean(CacheService.class));

    private final String rawText;
    private final List<IResolver> parts = new ArrayList<>();

    private MessageBuilder(String rawText) {
        this.rawText = rawText;
    }

    public static MessageBuilder from(String text) {
        if (text == null) {
            text = "";
        }
        String key = "mc.MessageBuilder." + text;
        CacheItem<MessageBuilder> cacheItem = cacheService.get().getCacheItem(key);
        if (cacheItem != null) {
            return cacheItem.getData();
        }
        MessageBuilder mr = new MessageBuilder(text);
        mr.resolveRawText();
        cacheItem = new CacheItem<>();
        cacheItem.setExpireAfterRead(60 * 1000000000L);
        cacheItem.setData(mr);
        cacheService.get().putCacheItem(key, cacheItem);
        return mr;
    }

    private void resolveRawText() {
        this.parts.clear();
        if (rawText == null) {
            return;
        }
        Matcher m = placeholderPattern.matcher(rawText);
        int preRegionEnd = 0;
        while (m.find()) {
            String var = m.group(1);
            int start = m.start(1);
            int end = m.end(1);
            if (start > preRegionEnd) {
                this.parts.add(new Constant(rawText.substring(Math.max(0, preRegionEnd), start)));
            }
            this.parts.add(new Placeholder(var));
            preRegionEnd = end;
        }
        if (preRegionEnd < rawText.length()) {
            this.parts.add(new Constant(rawText.substring(Math.max(0, preRegionEnd), rawText.length())));
        }
    }

    public Builder builder() {
        return new Builder();
    }

    @Override
    public String toString() {
        return rawText;
    }

    public String resolve(Map<String, Object> context) {
        return new Builder(context).build();
    }

    public String getRawText() {
        return rawText;
    }

    private static interface IResolver {
        String resolve(Map<String, Object> context);
    }

    private static class Constant implements IResolver {
        private final String var;

        public Constant(String var) {
            this.var = var;
        }

        @Override
        public String resolve(Map<String, Object> context) {
            return var;
        }
    }

    private static class Placeholder implements IResolver {
        private final String var;

        public Placeholder(String var) {
            this.var = var;
        }

        @Override
        public String resolve(Map<String, Object> context) {
            if (context == null) {
                return var;
            }
            Object val = context.get(var);
            if (val == null) {
                return var;
            } else {
                return val.toString();
            }
        }
    }

    public class Builder {
        private final Map<String, Object> context;

        public Builder() {
            context = new HashMap<>();
        }

        public Builder(Map<String, Object> context) {
            this.context = context == null ? new HashMap<>() : context;
        }

        public Builder var(String name, Object value) {
            context.put("${" + name + "}", value == null ? "" : value.toString());
            return this;
        }

        public Builder vars(Map<String, Object> context) {
            if (context != null) {
                context.forEach((name, value) -> {
                    var(name, value);
                });
            }
            return this;
        }

        public String build() {
            return toString();
        }

        private String resolve(Map<String, Object> context) {
            StringBuilder sb = new StringBuilder();
            for (IResolver part : parts) {
                sb.append(part.resolve(context));
            }
            return sb.toString();
        }

        @Override
        public String toString() {
            return resolve(context);
        }
    }
}
